package com.sunxiansheng.tool.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.sunxiansheng.tool.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * EasyExcel数据导出工具类
 */
@Slf4j
public class EasyExcelExporter<T> {

    private final Supplier<Long> dataTotalCountSupplier;
    private final BiFunction<Integer, Integer, List<T>> dataSupplier;
    private final List<List<String>> excelHead;
    private final long sheetDataRows;
    private final long writeDataRows;
    private final ExcelTypeEnum excelType;

    private EasyExcelExporter(Builder<T> builder) {
        if (builder.dataTotalCountSupplier == null) {
            throw new IllegalArgumentException("dataTotalCountSupplier cannot be null");
        }
        if (builder.dataSupplier == null) {
            throw new IllegalArgumentException("dataSupplier cannot be null");
        }
        if (builder.sheetDataRows <= 0) {
            throw new IllegalArgumentException("sheetDataRows must be greater than zero");
        }
        if (builder.writeDataRows <= 0) {
            throw new IllegalArgumentException("writeDataRows must be greater than zero");
        }

        this.dataTotalCountSupplier = builder.dataTotalCountSupplier;
        this.dataSupplier = builder.dataSupplier;
        this.excelHead = builder.excelHead != null ? builder.excelHead : extractHeadFromEntity(builder.dataSupplier.apply(0, (int) builder.sheetDataRows).get(0).getClass());
        this.sheetDataRows = builder.sheetDataRows;
        this.writeDataRows = builder.writeDataRows;
        this.excelType = builder.excelType;
    }

    /**
     * 导出到excel，支持大数量的分批写入
     * @param fileName 导出的文件名
     */
    public void exportExcel(String fileName) {
        HttpServletResponse response = SpringContextUtils.getHttpServletResponse();
        Long totalCount = dataTotalCountSupplier.get();
        log.info("Starting export of {} records to Excel file: {}", totalCount, fileName);
        doExport(response, fileName, totalCount);
    }

    private void doExport(HttpServletResponse response, String fileName, Long totalCount) {
        try (OutputStream outputStream = response.getOutputStream()) {
            ExcelWriter writer = EasyExcel.write(outputStream)
                    .head(excelHead)
                    .excelType(excelType)
                    .build();

            // 确保 writeDataRows 不大于 sheetDataRows
            long effectiveWriteDataRows = Math.min(writeDataRows, sheetDataRows);

            // 计算需要的Sheet数量
            long sheetNum = (totalCount + sheetDataRows - 1) / sheetDataRows;

            // 分批查询分次写入
            for (int i = 0; i < sheetNum; i++) {
                WriteSheet sheet = EasyExcel.writerSheet(i, fileName + (sheetNum == 1 ? "" : ("_" + i))).build();
                long startRow = i * sheetDataRows;
                long endRow = Math.min(startRow + sheetDataRows, totalCount);
                long oneSheetWriteCount = endRow - startRow;
                long pageCount = (oneSheetWriteCount + effectiveWriteDataRows - 1) / effectiveWriteDataRows;

                for (int page = 0; page < pageCount; page++) {
                    int offset = (int) (startRow + page * effectiveWriteDataRows);
                    int limit = (int) Math.min(effectiveWriteDataRows, endRow - offset);
                    List<T> dataList = dataSupplier.apply(offset, limit);
                    writer.write(dataList, sheet);
                }
            }

            setResponseHeaders(response, fileName);
            writer.finish();
            outputStream.flush();
            log.info("Excel export completed successfully for file: {}", fileName);
        } catch (Exception e) {
            log.error("EasyExcelExporter.exportWithBigData.error: {}", e.getMessage(), e);
        }
    }
    /**
     * 设置响应头信息
     * @param response HTTP响应对象
     * @param fileName 导出的文件名
     * @throws Exception 异常
     */
    private void setResponseHeaders(HttpServletResponse response, String fileName) throws Exception {
        response.setHeader("Content-Disposition", "attachment;filename="
                + new String((fileName).getBytes("gb2312"), "ISO-8859-1") + ".xlsx");
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
    }

    /**
     * 从实体类提取表头信息：有ExcelProperty注解则以注解的值优先
     * @param clazz 实体类的Class对象
     * @return 表头列表
     */
    private List<List<String>> extractHeadFromEntity(Class<?> clazz) {
        List<List<String>> head = new ArrayList<>();
        for (Field field : clazz.getDeclaredFields()) {
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            if (excelProperty != null && excelProperty.value().length > 0) {
                head.add(Collections.singletonList(excelProperty.value()[0]));
            } else {
                head.add(Collections.singletonList(field.getName()));
            }
        }
        return head;
    }

    /**
     * 参数解释：
     * dataTotalCountSupplier（必填）
     *  •  类型：Supplier<Long>
     *  •  作用：这是一个无参函数，返回数据的总数。通过这个参数，用户可以动态地提供要导出的数据总量。
     *  •  典型用途：在需要导出大量数据时，可以通过这个参数从数据库或其他数据源中获取数据总数，以便计算需要生成的 Excel sheet 数量和每个 sheet 中的数据量。
     * dataSupplier（必填）
     *  •  类型：BiFunction<Integer, Integer, List<T>>
     *  •  作用：这是一个函数，接受页码和每页大小参数，返回要导出的数据列表。每次调用这个函数时，会返回一部分数据，用于分批次写入 Excel 文件。
     *  •  典型用途：在需要分批次导出数据时，通过这个参数从数据库或其他数据源中获取数据。例如，如果一次性无法获取所有数据，可以分批次获取并写入 Excel 文件。
     * excelHead（默认：从实体类的属性中自动提取表头信息）
     *  •  类型：List<List<String>>
     *  •  作用：用于定义 Excel 文件的表头。优先级：传入的excelHead值 > 实体类中的注解@ExcelProperty注解值 > 默认：实体类中的属性名
     *  •  典型用途：用户可以自定义表头信息，以便在生成的 Excel 文件中包含特定的表头。例如，可以设置表头为 [“ID”, “名称”, “值”]。
     * sheetDataRows
     *  •  类型：long
     *  •  默认值：10000L
     *  •  作用：定义每个 Excel sheet 的数据行数。在分批写入数据时，这个参数决定了每个 sheet 的数据量。
     * writeDataRows
     *  •  类型：long
     *  •  默认值：2000L
     *  •  作用：定义每次写入 Excel sheet 的数据行数。在分批写入数据时，这个参数决定了每次写入的数据量。
     * excelType
     *  •  类型：ExcelTypeEnum
     *  •  默认值：ExcelTypeEnum.XLSX
     *  •  作用：定义要生成的 Excel 文件的类型。ExcelTypeEnum 枚举类提供了两种类型：XLS 和 XLSX。
     *  •  典型用途：根据需求生成不同类型的 Excel 文件。例如，可以选择生成 .xlsx 格式的文件（支持更大的数据量）或 .xls 格式的文件。
     */
    public static class Builder<T> {
        private final Supplier<Long> dataTotalCountSupplier;
        private final BiFunction<Integer, Integer, List<T>> dataSupplier;
        private List<List<String>> excelHead;
        private long sheetDataRows = 10000L;
        private long writeDataRows = 2000L;
        private ExcelTypeEnum excelType = ExcelTypeEnum.XLSX;

        /**
         * Builder的构造方法
         * @param dataTotalCountSupplier 数据总数的供应者
         * @param dataSupplier 数据列表的供应者
         */
        public Builder(Supplier<Long> dataTotalCountSupplier,
                       BiFunction<Integer, Integer, List<T>> dataSupplier) {
            if (dataTotalCountSupplier == null) {
                throw new IllegalArgumentException("dataTotalCountSupplier cannot be null");
            }
            if (dataSupplier == null) {
                throw new IllegalArgumentException("dataSupplier cannot be null");
            }
            this.dataTotalCountSupplier = dataTotalCountSupplier;
            this.dataSupplier = dataSupplier;
        }

        /**
         * 设置表头
         * @param excelHead 表头列表
         * @return Builder对象
         */
        public Builder<T> excelHead(List<String> excelHead) {
            if (excelHead == null || excelHead.isEmpty()) {
                throw new IllegalArgumentException("excelHead cannot be null or empty");
            }
            this.excelHead = excelHead.stream()
                    .map(Collections::singletonList)
                    .collect(Collectors.toList());
            return this;
        }

        public Builder<T> sheetDataRows(long sheetDataRows) {
            if (sheetDataRows <= 0) {
                throw new IllegalArgumentException("sheetDataRows must be greater than zero");
            }
            this.sheetDataRows = sheetDataRows;
            return this;
        }

        /**
         * 设置每次写入的数据行数
         * @param writeDataRows 每次写入的数据行数
         * @return Builder对象
         */
        public Builder<T> writeDataRows(long writeDataRows) {
            if (writeDataRows <= 0) {
                throw new IllegalArgumentException("writeDataRows must be greater than zero");
            }
            this.writeDataRows = writeDataRows;
            return this;
        }

        /**
         * 设置Excel文件类型
         * @param excelType Excel文件类型
         * @return Builder对象
         */
        public Builder<T> excelType(ExcelTypeEnum excelType) {
            if (excelType == null) {
                throw new IllegalArgumentException("excelType cannot be null");
            }
            this.excelType = excelType;
            return this;
        }

        /**
         * 构建EasyExcelExporter对象
         * @return EasyExcelExporter对象
         */
        public EasyExcelExporter<T> build() {
            return new EasyExcelExporter<>(this);
        }
    }
}